// SPDX-License-Identifier: GPL-2.0

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/fs.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/limits.h>

#include "dim_baseline_node.h"

#include "../common/dim_json.h"
#include "../common/dim_utils.h"

static const char *const dim_baseline_type_name[DIM_BASELINE_LAST] = {
	[DIM_BASELINE_USER] = "USER",
	[DIM_BASELINE_KERNEL] = "KERNEL",
	[DIM_BASELINE_TRAMPOLINE] = "TRAMPOLINE",
};

static enum dim_baseline_type dim_baseline_get_type_by_name(const char *name)
{
	int i;

	if (name == NULL)
		return DIM_BASELINE_LAST;

	for (i = 0; i < DIM_BASELINE_LAST; i++) {
		if (strcmp(dim_baseline_type_name[i], name) == 0)
			return i;
	}

	return DIM_BASELINE_LAST;
}

struct dim_baseline_node *dim_baseline_node_create(struct dim_baseline_handle *handle)
{
	struct dim_baseline_node *node = NULL;

	if (handle == NULL || handle->malloc_func == NULL)
		return ERR_PTR(-EINVAL);

	node = handle->malloc_func(sizeof(struct dim_baseline_node));
	if (node == NULL)
		return ERR_PTR(-ENOMEM);

	node->filename = NULL;
	node->data = NULL;
	node->action = ACTION_LOG;
	node->type = DIM_BASELINE_LAST;
	node->algo = DIM_HASH_LAST;

	return node;
}

void dim_baseline_node_destroy(struct dim_baseline_handle *handle,
			       struct dim_baseline_node *node)
{
	if (node == NULL || handle == NULL || handle->free_func == NULL)
		return;

	if (node->filename != NULL)
		handle->free_func(node->filename);

	if (node->data != NULL)
		handle->free_func(node->data);

	handle->free_func(node);
}

int dim_baseline_node_set_filename(struct dim_baseline_handle *handle,
				   struct dim_baseline_node *node,
				   const char *filename)
{
	int len;

	if (node == NULL || filename == NULL ||
		handle == NULL || handle->malloc_func == NULL)
		return -EINVAL;

	len = strlen(filename);
	if (len >= PATH_MAX)
		return -ENAMETOOLONG;

	node->filename = handle->malloc_func(len + 1);
	if (node->filename == NULL)
		return -ENOMEM;

	strcpy(node->filename, filename);
	return 0;
}

int dim_baseline_node_set_action(struct dim_baseline_handle *handle,
				struct dim_baseline_node *node,
				enum dim_core_policy_action action)
{
	if (node == NULL || action < 0 || action >= POLICY_ACTION_LAST)
		return -EINVAL;

	node->action = action;
	return 0;
}

int dim_baseline_node_set_type(struct dim_baseline_handle *handle,
			       struct dim_baseline_node *node,
			       enum dim_baseline_type type)
{
	if (node == NULL || type < 0 || type >= DIM_BASELINE_LAST)
		return -EINVAL;

	node->type = type;
	return 0;
}

int dim_baseline_node_set_type_str(struct dim_baseline_handle *handle,
				   struct dim_baseline_node *node,
				   const char *str)
{
	enum dim_baseline_type type;

	if (node == NULL || str == NULL)
		return -EINVAL;

	type = dim_baseline_get_type_by_name(str);
	if (type == DIM_BASELINE_LAST) {
		pr_err("invalid baseline type %s\n", str);
		return -EINVAL;
	}

	node->type = type;
	return 0;
}

int dim_baseline_node_set_hash_algo(struct dim_baseline_handle *handle,
				    struct dim_baseline_node *node,
				    enum dim_hash_type type)
{
	if (node == NULL || type < 0 || type >= DIM_HASH_LAST)
		return -EINVAL;

	node->algo = type;
	return 0;
}

int dim_baseline_node_set_hash_algo_str(struct dim_baseline_handle *handle,
					struct dim_baseline_node *node,
					const char *str)
{
	enum dim_hash_type tp = dim_hash_get_index_by_name(str);

	if (tp >= DIM_HASH_LAST) {
		pr_err("invalid baseline hash algo %s\n", str);
		return -EINVAL;
	}

	node->algo = tp;
	return 0;
}

static int dim_baseline_node_set_hash_data(unsigned char **dest,
					   enum dim_hash_type algo,
					   unsigned char *buf,
					   unsigned int buf_len,
					   dim_malloc malloc)
{
	unsigned int len;
	unsigned char *t_buf = NULL;

	len = dim_hash_get_digest_len(algo);
	if (len == 0 || len != buf_len || malloc == NULL)
		return -EINVAL;

	t_buf = malloc(buf_len);
	if (t_buf == NULL)
		return -ENOMEM;

	memcpy(t_buf, buf, buf_len);

	*dest = t_buf;
	return 0;
}

static int dim_baseline_node_set_hash_data_str(unsigned char **dest,
					       enum dim_hash_type algo,
					       const char *str,
					       dim_malloc malloc,
					       dim_free free)
{
	unsigned int len;
	unsigned char *buf = NULL;

	len = dim_hash_get_digest_len(algo);
	if (len == 0 || len != (strlen(str) >> 1))
		return -EINVAL;

	buf = malloc(len);
	if (buf == NULL)
		return -ENOMEM;

	if (dim_string_to_hex(str, strlen(str), buf, len) != 0) {
		free(buf);
		return -EINVAL;
	}

	*dest = buf;
	return 0;
}

int dim_baseline_node_set_main_hash_data(struct dim_baseline_handle *handle,
					 struct dim_baseline_node *node,
					 unsigned char *data,
					 unsigned int data_len)
{
	if (node == NULL || data == NULL ||
	    handle == NULL || handle->malloc_func == NULL)
		return -EINVAL;

	return dim_baseline_node_set_hash_data(&node->data, node->algo,
		data, data_len, handle->malloc_func);
}

int dim_baseline_node_set_main_hash_data_str(struct dim_baseline_handle *handle,
					     struct dim_baseline_node *node,
					     const char *str)
{
	if (node == NULL || str == NULL ||
	    handle == NULL || handle->malloc_func == NULL ||
	    handle->free_func == NULL)
		return -EINVAL;

	return dim_baseline_node_set_hash_data_str(&node->data, node->algo,
		str, handle->malloc_func, handle->free_func);
}

struct dim_baseline_handle *dim_baseline_handle_create(dim_rb_compare comp,
						       dim_malloc malloc,
						       dim_free free)
{
	struct dim_baseline_handle *handle = NULL;

	if (comp == NULL || malloc == NULL || free == NULL)
		return ERR_PTR(-EINVAL);

	handle = malloc(sizeof(struct dim_baseline_handle));
	if (handle == NULL)
		return ERR_PTR(-ENOMEM);

	handle->list = dim_rb_create(comp);
	if (handle->list == NULL) {
		free(handle);
		return ERR_PTR(-ENOMEM);
	}

	handle->malloc_func = malloc;
	handle->free_func = free;
	return handle;
}

void dim_baseline_handle_destroy(struct dim_baseline_handle *handle)
{
	struct dim_baseline_node *node = NULL;

	if (handle == NULL || handle->free_func == NULL)
		return;

	if (handle->list != NULL) {
		while (1) {
			node = (struct dim_baseline_node *)dim_rb_remove_first_data(handle->list);
			if (node == NULL)
				break;

			dim_baseline_node_destroy(handle, node);
		}
	}

	dim_rb_destroy(handle->list);
	handle->free_func(handle);
}

static int is_same_node(struct dim_baseline_node *node1,
			struct dim_baseline_node *node2)
{
	int len;

	if (node1->filename == NULL || node2->filename == NULL ||
	    node1->data == NULL || node2->data == NULL)
		return -1;

	if (strcmp(node1->filename, node2->filename) != 0 ||
	    node1->type != node2->type ||
	    node1->algo != node2->algo)
		return -1;

	len = dim_hash_get_digest_len(node1->algo);
	if (len != 0 && dim_digest_compare(node1->data, node2->data, len) == 0)
		return 0;

	return -1;
}

int dim_baseline_add_node(struct dim_baseline_handle *handle,
			  struct dim_baseline_node *node)
{
	struct dim_baseline_node *r_node = NULL;
	int ret;

	if (handle == NULL || node == NULL)
		return -EINVAL;

	ret = dim_rb_insert_data(handle->list, node, (void **)&r_node);
	if (ret == -EEXIST && r_node != NULL && is_same_node(node, r_node) == 0) {
		dim_baseline_node_destroy(handle, node);
		ret = 0;
	}

	return ret;
}

struct dim_baseline_node *dim_baseline_search_node(struct dim_baseline_handle *handle,
						   struct dim_baseline_node *node)
{
	if (handle == NULL || node == NULL)
		return NULL;

	return (struct dim_baseline_node *)dim_rb_find_data(handle->list, node);
}

int dim_baseline_node_compare(struct dim_baseline_node *node1, struct dim_baseline_node *node2)
{
	int ret;

	if (node1 == NULL || node2 == NULL || node1->filename == NULL || node2->filename == NULL)
		return -1;

	ret = strcmp(node1->filename, node2->filename);
	if (ret != 0)
		return ret;

	if (node1->type == node2->type)
		return 0;

	return node1->type > node2->type ? 1 : -1;
}
